using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace MIMPGameLibrary
{
    /// <summary>
    /// A Monster object represents a specific Monster in the game. Typically each Monster's name is unique
    /// to distinguish it from other Monsters which may be of the same type; however, enforcement of that
    /// guideline is left to the class holding the Monster objects.
    /// </summary>
    [Serializable]
    public class Monster
    {
        /// <summary>
        /// Defines the list of special abilities a Monster may have. Each Monster may have more than one ability.
        /// </summary>
        [Flags]
        public enum SpecialAbilities
        {
            None = 0,
            Swarm = 1,
            Flight = 2,
            Ferocity = 4,
            Diving = 8,
            Undead = 16,
            Necromancy = 32,
            Sorcery = 64,
            Giant = 128,
            Stealth = 256,
            Enchantment = 512
        }

        public enum Spells { Fireball, Lightning, Teleport, RaiseDead, Petrify, Cloak, Haste, Energize, Soar, Gills }

        private int maxHealth;
        /// <summary>
        /// Gets or sets the maximum health of the Monster.
        /// </summary>
        public int MaxHealth {
            get { return maxHealth; }
            set { maxHealth = value; }
        }
        private int health;
        /// <summary>
        /// Gets or sets the current health of the Monster. 
        /// </summary>
        public int Health {
            get { return health; }
            set { health = value > maxHealth ? maxHealth : value; }
        }
        /// <summary>
        /// True if the Monster's health is greater than zero, false otherwise.
        /// </summary>
        public bool IsAlive {
            get { return health > 0; }
        }
        private int attack;
        /// <summary>
        /// Gets or set the attack strength of the Monster.
        /// </summary>
        public int Attack {
            get { return attack; }
            set { attack = value; }
        }
        private int defense;
        /// <summary>
        /// Gets or sets the defense strength of the Monster.
        /// </summary>
        public int Defense {
            get { return defense; }
            set { defense = value; }
        }
        private int cost;
        /// <summary>
        /// Gets or sets the deployment cost of the Monster.
        /// </summary>
        public int Cost {
            get { return cost; }
            set { cost = value; }
        }
        private int speed;
        /// <summary>
        /// Gets or sets the speed of the Monster (number of Hexes it can move per turn).
        /// </summary>
        public int Speed {
            get { return speed; }
            set { speed = value; }
        }
        private int movesRemaining;
        /// <summary>
        /// Gets or sets the remaining number of times the Monster may move to an adjacent Hex this turn.
        /// </summary>
        public int MovesRemaining {
            get { return movesRemaining; }
            set { movesRemaining = value; }
        }
        private int actionsPerTurn;
        /// <summary>
        /// Gets or sets the number of actions per turn the Monster may take.
        /// </summary>
        public int ActionsPerTurn {
            get { return actionsPerTurn; }
            set { actionsPerTurn = value; }
        }
        private int actionsRemaining;
        /// <summary>
        /// Gets or sets the remaining number of times the Monster may take an action (attacking, 2nd move, etc) this turn.
        /// </summary>
        public int ActionsRemaining {
            get { return actionsRemaining; }
            set { actionsRemaining = value; }
        }
        private Hex location;
        /// <summary>
        /// Gets or sets the reference to the Hex that the Monster is on.
        /// </summary>
        public Hex Location {
            get { return location; }
            set { location = value; }
        }
        private Player controller;
        /// <summary>
        /// Gets or sets the Player who controls the Monster.
        /// </summary>
        public Player Controller {
            get { return controller; }
            set { controller = value; }
        }
        private string name;
        /// <summary>
        /// Gets or sets the name of the Monster.
        /// </summary>
        public string Name {
            get { return name; }
            set { name = value; }
        }
        private string type;
        /// <summary>
        /// Gets or sets the string that describes the type of the Monster.
        /// </summary>
        public string Type {
            get { return type; }
            set { type = value; }
        }
        private string icon;
        /// <summary>
        /// Gets or sets the name of the icon file to use to draw this Monster. 
        /// NOTE: the file must be one of those in the Sprites\Monsters directory--
        /// at this time you cannot import your own icons into the game.
        /// </summary>
        public string IconName {
            get { return icon; }
            set { icon = value; }
        }
        private Dictionary<Hex.TerrainType, int> costs;
        /// <summary>
        /// A Dictionary whose keys are Hex.TerrainTypes and whose values are the number of movement points this
        /// Monster must spend to move into a Hex of that TerrainType. A movement cost of 0 means the monster
        /// cannot enter that type of terrain.
        /// </summary>
        public Dictionary<Hex.TerrainType, int> MovementCosts {
            get { return costs; }
            set { costs = value; }
        }
        private SpecialAbilities abilities;
        /// <summary>
        /// A bit field that defines the special ability or abilities of this Monster.
        /// </summary>
        public SpecialAbilities Abilities {
            get { return abilities; }
            set { abilities = value; }
        }
        /// <summary>
        /// Constructs a new Monster.
        /// </summary>
        /// <param name="name">The Monster's name.</param>
        /// <param name="type">The Monster's type.</param>
        /// <param name="icon">Name of the icon file to use.</param>
        /// <param name="maxHealth">The Monster's maximum health value (also its initial current health).</param>
        /// <param name="attack">The Monster's attacking strength.</param>
        /// <param name="defense">The Monster's defending strength.</param>
        /// <param name="speed">The number of Hexes per turn the Monster may move.</param>
        /// <param name="actions">The number of actions the Monster may take each turn.</param>
        /// <param name="cost">The deployment cost of the Monster.</param>
        /// <param name="controller">The Player who controls the Monster.</param>
        /// <param name="abilities">A bit field that defines the special ability or abilities of this Monster.</param>
        public Monster(String name, String type, String icon, int maxHealth, int attack, int defense, int speed, int actions, int cost, Player controller, SpecialAbilities abilities) {
            this.name = name;
            this.type = type;
            this.IconName = icon;
            this.health = this.maxHealth = maxHealth;
            this.attack = attack;
            this.defense = defense;
            this.movesRemaining = this.speed = speed;
            this.actionsRemaining = this.actionsPerTurn = actions;
            this.cost = cost;
            this.controller = controller;
            this.costs = new Dictionary<Hex.TerrainType, int>(8);
            this.abilities = abilities;
        }

        /// <summary>
        /// Returns a string with the relevant stats of the Monster, given a game phase.
        /// </summary>
        /// <param name="phase">The current phase of the game (used because the Monster
        /// should report different info about itself in different phases)</param>
        /// <returns></returns>
        public string GetMonsterInfoString(GameState.Phases phase) {
            string text = "";
            if (phase == GameState.Phases.Setup) {
                text = "Type: " + type;
                text += String.Format("\nHP:{0:d2}  MV:{1:d2}  A:{2:d2}  D:{3:d2}", maxHealth, speed, attack, defense);
                text += "\nActions/turn: " + actionsPerTurn + "\nCost: " + cost + "\n";
            } else if (phase == GameState.Phases.Play) {
                text = name + " (" + type + ")";
                text += String.Format("\nHP:{0:d2}/{1:d2}  A:{2:d2}  D:{3:d2}", health, maxHealth, attack, defense);
                text += String.Format("\nMoves: {0}/{1}\nActions: {2}/{3}\n", movesRemaining, speed, actionsRemaining, actionsPerTurn);
            }
            bool comma = false;
            foreach (SpecialAbilities ability in Enum.GetValues(typeof(SpecialAbilities))) {
                if ((ability & this.Abilities) != 0) {
                    string name = Enum.GetName(typeof(SpecialAbilities), ability);
                    text += comma ? ", " + name : name;
                    comma = true;
                }
            }
            return text;
        }
        /// <summary>
        /// Returns a string that looks like "Name (Type)".
        /// </summary>
        /// <returns></returns>
        public override string ToString() {
            if (this.name == "") {
                return this.type;
            } else if (this.type == "") {
                return this.name;
            } else {
                return this.name + " (" + this.Type + ")";
            }
        }
        /// <summary>
        /// Sets the movement cost for the given terrain type to the given value.
        /// </summary>
        /// <param name="terrain">The terrain type to set the cost for.</param>
        /// <param name="cost">The value to set the cost to.</param>
        public void SetMovementCost(Hex.TerrainType terrain, int cost) {
            costs[terrain] = cost;
        }
        /// <summary>
        /// Invokes this Monster's MemberwiseClone method and returns the result as a Monster.
        /// </summary>
        /// <returns>A shallow copy of this Monster.</returns>
        public Monster ShallowCopy() {
            return (Monster)(this.MemberwiseClone());
        }
        /// <summary>
        /// Looks through all the XMLs in the given directory that match the "Monster.xsd" schema, 
        /// and builds a List of Monsters out of them.
        /// </summary>
        /// <param name="directory">The directory with the Monster.xsd schema and the XMLs that match it.</param>
        /// <returns>A List of Monsters, one for each XML in the directory.</returns>
        public static List<Monster> CreateMonstersFromXMLs(String directory) {
            DirectoryInfo dir = new DirectoryInfo(directory);
            if (!dir.Exists) { throw new System.IO.DirectoryNotFoundException(); }

            List<Monster> monsters = new List<Monster>();

            //TODO: Validate XML before reading it
            foreach (FileInfo file in dir.GetFiles("*.xml")) {
                XmlReaderSettings settings = new XmlReaderSettings();
                settings.CloseInput = true;
                settings.ConformanceLevel = ConformanceLevel.Document;
                settings.ValidationType = ValidationType.Schema;
                settings.Schemas.Add(null, directory + "Monster.xsd");
                settings.ValidationFlags |= System.Xml.Schema.XmlSchemaValidationFlags.ReportValidationWarnings;

                XmlReader reader = XmlReader.Create(file.Open(FileMode.Open), settings);
                reader.MoveToContent();
                reader.ReadStartElement();
                reader.ReadStartElement("Icon");
                String iconFile = reader.ReadContentAsString();
                reader.ReadEndElement();
                reader.ReadStartElement("Type");
                String type = reader.ReadContentAsString();
                reader.ReadEndElement();
                reader.ReadStartElement("MaxHealth");
                int maxHealth = reader.ReadContentAsInt();
                reader.ReadEndElement();
                reader.ReadStartElement("Attack");
                int attack = reader.ReadContentAsInt();
                reader.ReadEndElement();
                reader.ReadStartElement("Defense");
                int defense = reader.ReadContentAsInt();
                reader.ReadEndElement();
                reader.ReadStartElement("MovementPoints");
                int speed = reader.ReadContentAsInt();
                reader.ReadEndElement();
                reader.ReadStartElement("Actions");
                int actions = reader.ReadContentAsInt();
                reader.ReadEndElement();

                int spellPoints = 0;
                try {
                    reader.ReadStartElement("SpellPoints");
                    spellPoints = reader.ReadContentAsInt();
                    reader.ReadEndElement();
                } catch (XmlException) {
                }

                reader.ReadStartElement("DeploymentCost");
                int cost = reader.ReadContentAsInt();
                reader.ReadEndElement();
                reader.ReadStartElement("TerrainMovementCosts");
                int[] movementCosts = new int[8];
                Monster m = new Monster("", type, iconFile, maxHealth, attack, defense, speed, actions, cost, null, 0);
                foreach (Hex.TerrainType terrain in Enum.GetValues(typeof(Hex.TerrainType))) {
                    reader.Read();
                    reader.MoveToAttribute("type");
                    if ("field".Equals(reader.Value.ToLower())) {
                        reader.Read();
                        m.SetMovementCost(Hex.TerrainType.Field, reader.ReadContentAsInt());
                        reader.ReadEndElement();
                    } else if ("forest".Equals(reader.Value.ToLower())) {
                        reader.Read();
                        m.SetMovementCost(Hex.TerrainType.Forest, reader.ReadContentAsInt());
                        reader.ReadEndElement();
                    } else if ("grass".Equals(reader.Value.ToLower())) {
                        reader.Read();
                        m.SetMovementCost(Hex.TerrainType.Grass, reader.ReadContentAsInt());
                        reader.ReadEndElement();
                    } else if ("hills".Equals(reader.Value.ToLower())) {
                        reader.Read();
                        m.SetMovementCost(Hex.TerrainType.Hills, reader.ReadContentAsInt());
                        reader.ReadEndElement();
                    } else if ("mountain".Equals(reader.Value.ToLower())) {
                        reader.Read();
                        m.SetMovementCost(Hex.TerrainType.Mountain, reader.ReadContentAsInt());
                        reader.ReadEndElement();
                    } else if ("swamp".Equals(reader.Value.ToLower())) {
                        reader.Read();
                        m.SetMovementCost(Hex.TerrainType.Swamp, reader.ReadContentAsInt());
                        reader.ReadEndElement();
                    } else if ("tundra".Equals(reader.Value.ToLower())) {
                        reader.Read();
                        m.SetMovementCost(Hex.TerrainType.Tundra, reader.ReadContentAsInt());
                        reader.ReadEndElement();
                    } else if ("water".Equals(reader.Value.ToLower())) {
                        reader.Read();
                        m.SetMovementCost(Hex.TerrainType.Water, reader.ReadContentAsInt());
                        reader.ReadEndElement();
                    }
                }
                //read </TerrainMovementCosts>
                reader.ReadEndElement();

                reader.ReadStartElement("Abilities");
                Monster.SpecialAbilities abils = SpecialAbilities.None;
                //Read abilities until we get an XmlException, thrown when we try to read an <ability> element 
                //but there are not any left.
                try {
                    while (true) {
                        reader.ReadStartElement("ability");
                        string abilityName = reader.ReadContentAsString().ToLower();
                        //find the ability enum that goes with this ability name
                        foreach (Monster.SpecialAbilities ability in Enum.GetValues(typeof(Monster.SpecialAbilities))) {
                            if (Enum.GetName(typeof(Monster.SpecialAbilities), ability).ToLower() == abilityName) {
                                abils |= ability;
                                continue;
                            }
                        }
                        reader.ReadEndElement();
                    }
                } catch (XmlException) { } finally {
                    m.abilities = abils;
                }

                monsters.Add(m);
                reader.Close();
            }
            return monsters;
        }
    }
}
